// bind:
// bind() 方法会创建一个新函数。
// 当这个新函数被调用时，
// bind() 的第一个参数将作为它运行时的 this，
// 之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )

// bind 函数的两个特点：

// 1. 返回一个函数
// 2. 可以传入参数

// bind 最难特点
// 一个绑定函数也能使用new操作符创建对象：
// 这种行为就像把原函数当成构造器。
// 提供的 this 值被忽略，
// 同时调用时的参数被提供给模拟函数。

// 举例

// var foo = {
//   value: 1
// };

// function bar() {
//   console.log(this.value);
// }

// // 返回了一个函数
// var bindFoo = bar.bind(foo);

// bindFoo(); // 1

Function.prototype.bind2 = function (context, ...args) {
  const fn = this;

  // newFn这里是一个构造函数，改成箭头函数，newFn 不再是一个构造函数，
  // 因此 this instanceof newFn 的判断将始终返回 false
  function newFn(...newArgs) {
    const isNew = this instanceof newFn;
    fn.apply(isNew ? this : context, args.concat(newArgs));
  }
  newFn.prototype = fn.prototype

  return newFn
};

// 第三版
// Function.prototype.bind2 = function (context) {
//   var self = this;
//   var args = Array.prototype.slice.call(arguments, 1);

//   var fbound = function () {
//     var bindArgs = Array.prototype.slice.call(arguments);
//     // 当作为构造函数时，this 指向实例，self 指向绑定函数，因为下面一句 `fbound.prototype = this.prototype;`，
//     // 已经修改了 fbound.prototype 为 绑定函数的 prototype，此时结果为 true，当结果为 true 的时候，this 指向实例。
//     // 当作为普通函数时，this 指向 window，self 指向绑定函数，此时结果为 false，当结果为 false 的时候，this 指向绑定的 context。
//     self.apply(this instanceof self ? this : context, args.concat(bindArgs));
//   };
//   // 修改返回函数的 prototype 为绑定函数的 prototype，实例就可以继承函数的原型中的值
//   fbound.prototype = this.prototype;
//   return fbound;
// };

